/* Copyright (c) 2022-2022, LiWangQian<liwangqian@huawei.com> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef LIBDAG_LINK_H
#define LIBDAG_LINK_H

#include <forward_list>
#include "libdag/io_node.hpp"
#include "libdag/element.hpp"
#include "libdag/readable.hpp"
#include "libdag/writable.hpp"

namespace libdag {

class link : public element, readable, writable {
public:
    using location = std::pair<io_node_ptr, size_t>;
    static constexpr location null_location = {nullptr, 0};

    virtual ~link()
    {
        if (source_.first) {
            (void)source_.first->set_output(source_.second, nullptr);
        }

        for (auto &loc : sinks_) {
            if (loc.first) {
                (void)loc.first->set_input(loc.second, nullptr);
            }
        }
    }

    bool connect(io_node_ptr from, size_t from_index,
        io_node_ptr to, size_t to_index)
    {
        if (from == nullptr || to == nullptr) {
            return false;
        }

        if (from->set_output(from_index, this)) {
            source_ = {from, from_index};
            if (to->set_input(to_index, this)) {
                sinks_.push_front({to, to_index});
                sinks_count_++;
                return true;
            } else {
                source_ = null_location;
            }
        }
        return false;
    }

    auto source_count() const noexcept
    {
        return source_ != null_location ? 1 : 0;
    }

    auto sink_count() const noexcept
    {
        return sinks_count_;
    }

    link(const link &) = delete;
    link &operator=(const link &) = delete;
    link(link &&) noexcept = default;
    link &operator=(link &&) noexcept = default;

protected:
    link() = default;

    location source_{};
    std::forward_list<location> sinks_{};
    size_t sinks_count_{0};
};

using link_ptr = link *;

inline bool connect(link_ptr lk, io_node_ptr from, size_t from_index, io_node_ptr to, size_t to_index)
{
    return lk ? lk->connect(from, from_index, to, to_index) : false;
}

} // namespace libdag

#endif
